#include "FrameBufferObject.h"
#include <GL/glew.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>

namespace Tmpl {

// Error helper function
void GetFrameBufferErrorString( GLenum status, char* tmps, unsigned int maxString )
{
	switch(status)
	{
	case GL_FRAMEBUFFER_COMPLETE:
		strcpy_s( tmps, maxString, "Framebuffer complete");
		return;

	case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
		strcpy_s( tmps, maxString, "Framebuffer incomplete: Attachment is NOT complete" );
		return;

	case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
		strcpy_s( tmps, maxString, "Framebuffer incomplete: No image is attached to FBO");
		return;

	case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
		strcpy_s( tmps, maxString, "Framebuffer incomplete: Draw buffer" );
		return;

	case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
		strcpy_s( tmps, maxString, "Framebuffer incomplete: Read buffer" );
		return;

	case GL_FRAMEBUFFER_UNSUPPORTED:
		strcpy_s( tmps, maxString, "Unsupported by FBO implementation" );
		return;

	default:
		strcpy_s( tmps, maxString, "Unknown error" );
		return;
	}
}

// FBO
FrameBufferObject::FrameBufferObject()
{
	mRenderTargetCount = 0;
	mFboId = 0;
	mTextureId = 0;
	mDepthId = 0;
	mWidth = 0;
	mHeight = 0;
}

FrameBufferObject::~FrameBufferObject()
{
	for(int i=0; i<mRenderTargetCount; ++i)
	{
		glDeleteTextures(1, &mTextureId[i]);
	}
	if (mTextureId) delete [] mTextureId;
	if (mDepthId) glDeleteTextures(1, &mDepthId );
	if (mFboId) glDeleteFramebuffers(1, &mFboId);
}

bool FrameBufferObject::Init( int width, int height, int renderTargetCount, unsigned int pixelFormat, bool filter )
{
	// clean up old one if it exists
	for(int i=0; i<mRenderTargetCount; ++i)
	{
		glDeleteTextures(1, &mTextureId[i]);
	}
	if (mTextureId) delete [] mTextureId;
	if (mDepthId) glDeleteTextures(1, &mDepthId );
	if (mFboId) glDeleteFramebuffers(1, &mFboId);

	// set new params
	mWidth = width;
	mHeight = height;
	mRenderTargetCount = renderTargetCount;
	mTextureId = new GLuint[mRenderTargetCount];

	// Create FBO
	glGenFramebuffers(1, &mFboId);
	glBindFramebuffer(GL_FRAMEBUFFER, mFboId);

	// Init render targets
	for( int i=0; i<mRenderTargetCount; ++i )
	{
		glGenTextures(1, &(mTextureId[i]));
		glBindTexture(GL_TEXTURE_2D, mTextureId[i]);

		if( filter )
		{
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		}
		else
		{
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		}

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		GLenum pixelType = GL_UNSIGNED_BYTE;
		if( pixelFormat == GL_RGBA16F_ARB )
		{
			pixelType = GL_FLOAT;
		}
		else if( pixelFormat == GL_RGBA8 )
		{
			pixelType = GL_UNSIGNED_BYTE;
		}
		else
		{
			assert( 0 );		// only GL_RGBA8 and GL_RGBA16F_ARB supported for now!
		}
		glTexImage2D(GL_TEXTURE_2D, 0, pixelFormat, width, height, 0, GL_RGBA, pixelType, 0);
		glBindTexture(GL_TEXTURE_2D, 0);

		// attach a texture to FBO color attachement point
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+i, GL_TEXTURE_2D, mTextureId[i], 0);
	}

	// Init Depthbuffer
	glGenTextures(1, &mDepthId );
	glBindTexture(GL_TEXTURE_2D, mDepthId );

	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, mDepthId, 0);

	// check FBO status
	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if(status != GL_FRAMEBUFFER_COMPLETE)
	{
		char tmps[256];
		GetFrameBufferErrorString( status, tmps, 256 );

		char tmps2[256];
		sprintf_s( tmps2, 256, "FBO not setup OK: '%s'", tmps );

		assert( !tmps2 );
		return false;
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	return true;
}

void FrameBufferObject::BindTexture( int index )
{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, mTextureId[index]);
}
void FrameBufferObject::BindDepthTexture()
{
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, mDepthId );
}
void FrameBufferObject::BindAllTextures()
{
	for (int i=0; i<mRenderTargetCount; ++i)
	{
		glActiveTexture(GL_TEXTURE0 + i);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, mTextureId[i]);
	}
	glActiveTexture(GL_TEXTURE0 + mRenderTargetCount);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, mDepthId);
}
void FrameBufferObject::UnbindAllTextures()
{
	glActiveTexture(GL_TEXTURE0 + mRenderTargetCount);
	glDisable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	for (int i=mRenderTargetCount-1; i>=0; --i)
	{
		glActiveTexture(GL_TEXTURE0 + i);
		glDisable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, 0);
	}
}

void FrameBufferObject::Bind()
{
	glBindFramebuffer(GL_FRAMEBUFFER, mFboId);
}
void FrameBufferObject::Unbind()
{
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

}